package fybug.nulll.pdstream.WR.Stream;
import com.sun.istack.internal.NotNull;
import com.sun.istack.internal.Nullable;

import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.Writer;

import fybug.nulll.pdfunctionlibrary.Processing.Check;
import fybug.nulll.pdfunctionlibrary.Processing.Err.Stop;
import fybug.nulll.pdfunctionlibrary.Util.Processing.Arrarys;
import fybug.nulll.pdstream.Interface.UpdateSize;
import fybug.nulll.pdstream.Source.StreamSource;
import fybug.nulll.pdstream.StreamOperator;
import fybug.nulll.pdstream.WR.Err.CharStreamErr;
import fybug.nulll.pdstream.WR.W_R;
/**
 * <h2>字符流写入器.</h2>
 * <pre>
 * <b>建议使用工场类 {@link W_R} 构造</b>
 *
 * 从绑定的 <b>操作源</b> 中获取的被操作流和绑定字符集合进行写入
 * 支持 <b>单个数据写入多个流</b>
 * 支持 <b>逐行写入</b> ，会标记当前写入位置
 * 可传入 <b>不绑定在源中</b> 的要写入的字符
 * 可使用该操作器中的方法对操作源进行 <b>流和字符的重绑定</b>
 * 可以使用 <b>连续操作</b> ,有效解决不稳定流的输出问题
 *
 * <b>不要将片段大小设置的太大</b>
 *  </pre>
 * <pre>常规操作方法：
 * {@link #write()}
 * {@link #write(CharSequence)}
 * </pre>
 *
 * @author fybug
 * @version 0.2.0
 * @see StreamOperator
 * @see Writer
 * @see CharStreamErr
 * @see W_R
 * @since JDK 1.8
 */
@SuppressWarnings( "all" )
public final
class Write extends StreamOperator<Writer, CharSequence> {
    // 字符操作位置
    private int charMark = 0;

    /**
     * <p>构造一个写入器并绑定源.</p>
     *
     * @param r 要被操作器写入的操作源
     */
    public
    Write(@Nullable final StreamSource<Writer, CharSequence> r) { binSource(r); }

    /*
     * MainSource
     */

    /**
     * <p>绑定要写入的文本源.</p>
     *
     * @param r 要被操作器写入的操作源
     *
     * @return this
     */
    @NotNull
    public final
    Write binSource(@Nullable final StreamSource<Writer, CharSequence> r)
    {
        synchronized ( this ){
            charMark = 0;
            super.binSource(r);
        }
        return this;
    }

    /**
     * <p>重绑定操作源中的流.</p>
     *
     * @param r 要绑定到被绑定的源中的流
     *
     * @return this
     */
    @NotNull
    public final
    Write binWriter(@Nullable final Writer... r) {
        try {
            source().binStream(r);
        } catch ( Stop ignored ) {
        }
        return this;
    }

    /**
     * <p>增加操作源中的流.</p>
     *
     * @param r 要追加到被绑定的源中的流
     *
     * @return this
     */
    @NotNull
    public final
    Write appendWriter(final Writer... r) {
        try {
            @NotNull final StreamSource<Writer, CharSequence> source = source();
            Writer[] writers;
            try {
                writers = source.stream();
            } catch ( Stop e ) {
                writers = null;
            }
            binWriter(Arrarys.append(writers, r));
        } catch ( Stop ignored ) {
        }
        return this;
    }

    /**
     * <p>重绑定操作源中的字符集合.</p>
     *
     * @param c 要绑定到被绑定的源中的字符集合
     *
     * @return this
     */
    @NotNull
    public final
    Write binString(@Nullable final CharSequence c) {
        try {
            synchronized ( this ){
                charMark = 0;
                source().binOutPut(c);
            }
        } catch ( Stop ignored ) {
        }
        return this;
    }

    /*
     * Write
     */

    /**
     * <p>将绑定的字符集合写入到绑定的流中.</p>
     * <pre>
     * 获取源中绑定的字符 <b>根据记录位置裁剪</b> 并写入到绑定的所有流中
     * 适用于稳定的流
     * </pre>
     *
     * @return this
     *
     * @throws CharStreamErr 写入过程发生错误
     */
    @NotNull
    public final
    Write write() throws CharStreamErr
    {
        try {
            synchronized ( this ){
                write(source().getOutPut().toString().substring(charMark));
                charMark = source().getOutPut().toString().length();
            }
        } catch ( Stop | StringIndexOutOfBoundsException ignored ) {
        }
        return this;
    }

    /**
     * <p>将字符集合写入到绑定的流中.</p>
     * <pre>
     * 将传入的字符写入到绑定的所有流中
     * 适用于稳定的流
     * </pre>
     *
     * @param c 要写入的字符集合
     *
     * @return this
     *
     * @throws CharStreamErr 写入过程发生错误
     */
    @NotNull
    public final
    Write write(@Nullable final CharSequence c) throws CharStreamErr {
        @NotNull Writer[] ws;
        @NotNull final String s = c.toString();
        @Nullable Writer w = null; // 减少寻址
        try {
            synchronized ( this ){
                ws = source().stream();
                Check.checkNull(c);
                for ( int i = 0; i < ws.length; i++ ){
                    /* 批量写入 */
                    w = ws[i];
                    // 写入
                    w.write(s);
                    // 刷新
                    w.flush();
                }
            }
        } catch ( IOException e ) {
            throw new CharStreamErr(e.getMessage(), null, w);
        } catch ( Stop ignored ) {
        }
        return this;
    }

    /**
     * <p>将绑定的字符集合中的一行写入到绑定的流中.</p>
     * <pre>
     * 获取源中绑定的字符 <b>根据记录位置裁剪</b> 并写入到绑定的所有流中
     * 在遇到集合尾或{@code "\n"} 会结束读取，并标记当前位置
     * 适用于所有流
     * </pre>
     *
     * @return this
     *
     * @throws CharStreamErr 写入过程发生错误
     */
    @NotNull
    final public
    Write writeLine() throws CharStreamErr {
        try {
            @NotNull CharArrayWriter bufferedWriter = new CharArrayWriter(); // 缓存
            @NotNull String s; // 写入内容
            char c; // 减少寻址
            synchronized ( this ){
                s = source().getOutPut().toString().substring(charMark);
                for ( int i = 0, leng = s.length(); i < leng; ){
                    c = s.charAt(i);
                    i++;
                    charMark++;
                    bufferedWriter.write(c);
                    if (c == '\n') {
                        /* 检查是否到行尾 */
                        break;
                    }
                }
                c = 1;
                s = null;
                write(bufferedWriter.toString());
            }
        } catch ( Stop | StringIndexOutOfBoundsException ignored ) {
        }
        return this;
    }

    /**
     * <p>使用连续操作将绑定的字符写入到绑定的流中.</p>
     * <pre>
     * 将标记位置之后的数据写入到 <b>绑定的所有流</b> 中，每次写入一个片段
     * 适用于所有流
     * </pre>
     *
     * @param size 片段大小
     *
     * @return this
     *
     * @throws CharStreamErr 写入过程出错
     */
    @NotNull
    public final
    Write sustained(final int size) throws CharStreamErr
    { return monitor(size, checkUpSize()); }

    /**
     * <p>使用连续操作将绑定的字符写入到绑定的流中.</p>
     * <pre>
     * 将标记位置之后的数据写入到 <b>绑定的所有流</b> 中，每次写入一个片段
     * 可以使用 <b>中介接口</b> <b>动态调整片段</b> 大小
     * 在写入过程中设置片段大小为{@code 0} 可以中断操作
     * 适用于所有流
     * </pre>
     *
     * @param size 初始大小
     * @param see  中介接口
     *
     * @return this
     *
     * @throws CharStreamErr 写入过程出错
     * @see UpdateSize
     */
    @NotNull
    public final
    Write monitor(int size, @Nullable final UpdateSize see) throws CharStreamErr
    {
        @NotNull Writer[] r;
        @NotNull String stringCache;
        int cacheLenght; // 长度缓存
        @NotNull String s;
        @Nullable Writer w = null; // 减少寻址
        int now = 0; // 当前指针
        try {
            if (see != null)
                see.init();
            synchronized ( this ){
                r = source().stream(); // 写入流
                stringCache = source().getOutPut().toString().substring(charMark); // 写入内容
                if (size <= 0)
                    return this;
                cacheLenght = stringCache.length();
                while( now < cacheLenght ){
                    /* 还有剩余 */
                    //* 剩余长度大于等于缓冲区 *//
                    s = stringCache.substring(now, cacheLenght - now >= size ? now += size
                            : (now = cacheLenght)); // 缓存当前写入内容
                    for ( int i = 0; i < r.length; i++ ){
                        /* 写入所有流 */
                        w = r[i];
                        w.write(s);
                    }
                    s = null;
                    // 更新片段大小
                    size = see == null ? size : see.updataSize(size);
                }
                now = 1;
                stringCache = null;
                charMark += cacheLenght;
                for ( int i = 0; i < r.length; i++ ){
                    w = r[i];
                    // 刷新
                    w.flush();
                }
            }
            cacheLenght = 1;
            w = null;
        } catch ( Stop | StringIndexOutOfBoundsException ignored ) {
        } catch ( IOException e ) {
            throw new CharStreamErr(e.getMessage(), null, w);
        } finally {
            if (see != null)
                see.destroy();
        }
        return this;
    }
}
